﻿using PNet;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;

namespace PNet
{
    /// <summary>
    /// 接收服务器与客户端之间的会话
    /// </summary>
    public abstract class IOCPToken<T> where T : IOCPMsg,new()
    {
        public int TokenID;
        public enum TokenState
        {
            None = 0,
            Connected,
            Disconnected,
        }


        private SocketAsyncEventArgs mRcvArgs;
        private SocketAsyncEventArgs mSndArgs;
        private Socket mSocket;
        private TokenState mTokenState = TokenState.None;
        private List<byte> mByteList = new List<byte>();
        public bool mIsWrite;
        private Queue<byte[]> mWriteBytesQue = new Queue<byte[]>();
        public Action<int> OnTokenClose;
        public void InitToken(Socket socket)
        {
            this.mSocket = socket;
            mTokenState = TokenState.Connected;
            mRcvArgs = new SocketAsyncEventArgs();
            mSndArgs = new SocketAsyncEventArgs();
            mSndArgs.Completed += IO_Completed;
            mRcvArgs.Completed += IO_Completed;
            mRcvArgs.SetBuffer(new byte[2048],0,2048);
            Connected();
            StartAsAccept();
        }



        public void StartAsAccept()
        {
            bool isPending = mSocket.ReceiveAsync(mRcvArgs);

            if (!isPending)
            {
                ProcessReceive();
            }
        }

        public bool SendMsg(T msg)
        {
            byte[] data = IOCPTool.PackIenInfo(IOCPTool.Serialize<T>(msg));
            return SendMsg(data);
        }
    
        
        public bool SendMsg(byte[] bytes)
        {
            if (mTokenState == TokenState.Connected)
            {
                if (mIsWrite)
                {
                    mWriteBytesQue.Enqueue(bytes);
                    return true;
                }
                mIsWrite = true;
                mSndArgs.SetBuffer(bytes, 0, bytes.Length);
                bool isPending = mSocket.SendAsync(mSndArgs);
                if (!isPending)
                {
                    ProcessSend();
                }
           

            }
            else 
            {
                IOCPTool.LogWarning("Connection is break, cannot send net msg");
                return false;
            }

            return true;
        }

        


        public void ProcessReceive()
        {
            if (mRcvArgs.BytesTransferred > 0 && mRcvArgs.SocketError == SocketError.Success)
            {
                byte[] setBuffer = new byte[mRcvArgs.BytesTransferred];
                Buffer.BlockCopy(mRcvArgs.Buffer, 0, setBuffer, 0, mRcvArgs.BytesTransferred);
                mByteList.AddRange(setBuffer);
                ProcessBytesList();
                StartAsAccept();
            }
            else
            {
                CloseToken();
            }
            
    

        }

        public void ProcessSend()
        {
            if (mSndArgs.SocketError == SocketError.Success)
            {
                mIsWrite = false;
                if (mWriteBytesQue.Count > 0)
                {
                    byte[] newBytes = mWriteBytesQue.Dequeue();
                    SendMsg(newBytes);
                }
            }
            
        }

        public void ProcessBytesList()
        {
            byte[] rvcBuffer = IOCPTool.SplitLogicByte(ref mByteList);
            if (rvcBuffer != null)
            {
                T msg = IOCPTool.DeSerialize<T>(rvcBuffer);
                OnReceiveMsg(msg);
                ProcessBytesList();
            }
        }
        private void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
      
                case SocketAsyncOperation.Receive:
                    ProcessReceive();
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend();
                    break;
                default:
                    IOCPTool.LogWarning("Socket Event Error:" + e.LastOperation);
                    break;
            }
        }


        protected abstract void Connected();
        protected abstract void OnReceiveMsg(T msg);
        protected abstract void OnDisconnected();
       

        public void CloseToken()
        {
            mTokenState = TokenState.Disconnected;
            OnDisconnected();
            mByteList.Clear();
            mWriteBytesQue.Clear();
            mIsWrite = false;
            OnTokenClose?.Invoke(TokenID);
            try
            {
                mSocket.Shutdown(SocketShutdown.Send);
            }
            catch (Exception e)
            {

                IOCPTool.LogError("Close token error:" + e.Message);
            }
            finally 
            {
                mSocket.Close();
                if (mSocket != null)
                {
                    mSocket = null;
                }
              
            }
        }
    }
}
